###########################################################################
## Python code generated with wxFormBuilder (version Oct 26 2018)
## http://www.wxformbuilder.org/
## 微信备份浏览程序,只针对apple导出数据
## PLEASE DO *NOT* EDIT THIS FILE!
## xiaoyf 2024.09.27
## Documents目录下已经没有那个LocalInfo.lst文件
## pyinstaller -F d:\pythonp\wx2\weixin_view_win_v3_0.py
## pyinstaller -F weixin_view_mac_v3_0.py
# 由于运行编译程序时报   File "wx/richtext.py", line 17, in <module>
# ModuleNotFoundError: No module named 'wx._xml' 错误，所以使用下面的命令编译程序
# 在 PyInstaller 命令中添加 --hidden-import wx._xml 参数，以明确告诉 PyInstaller 需要包含这个模块
#  pyinstaller --hidden-import wx._xml weixin_view_mac_v4_0.py
###########################################################################

import wx
import wx.richtext
import hashlib
import plistlib
import sqlite3
import os
import sys
import re
from bs4 import BeautifulSoup
import time, datetime
import xml.etree.ElementTree as ET
import shutil
import lz4.block as lb
from sympy.strategies.branch import notempty

global rdb
global filelst_path
global wpath
global wxidlist

class WeChatContactViewer(wx.Frame):
    def __init__(self):
        super().__init__(None, title='微信联系人查看器', size=(1124, 753))

        # 添加一个面板用于放置控件
        panel = wx.Panel(self)
        # 设置面板的背景颜色为白色
        panel.SetBackgroundColour(wx.Colour(50, 50, 50))

        # 创建一个配置文件显示框
        self.config_file_display = wx.TextCtrl(panel, style=wx.TE_READONLY)
        self.config_file_display.SetBackgroundColour(wx.Colour(50, 50, 50))  # 黑色背景
        self.config_file_display.SetForegroundColour(wx.Colour(255, 255, 255))  # 白色文字

        # 创建一个选择文件的按钮
        self.select_file_button = wx.Button(panel, label='选择微信md5字串目录')
        self.select_file_button.Bind(wx.EVT_BUTTON, self.on_select_file)
        self.select_file_button.SetBackgroundColour(wx.Colour(50, 50, 50))  # 黑色背景
        self.select_file_button.SetForegroundColour(wx.Colour(255, 255, 255))  # 白色文字

        # 联系人列表框
        self.contact_list = wx.ListBox(panel)
        self.contact_list.SetBackgroundColour(wx.Colour(50, 50, 50))  # 黑色背景
        self.contact_list.SetForegroundColour(wx.Colour(255, 255, 255))  # 白色文字

        #self.contact_list.Bind(wx.EVT_LISTBOX, self.on_contact_select)
        self.contact_list.Bind(wx.EVT_LISTBOX_DCLICK, self.on_contact_select)

        # 聊天展示区域
        self.chat_display = wx.richtext.RichTextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY)
        #self.chat_display = wx.StaticText(panel, style=wx.TE_MULTILINE | wx.TE_READONLY)
        self.chat_display.SetBackgroundColour(wx.Colour(50, 50, 50))  # 黑色背景
        self.chat_display.SetForegroundColour(wx.Colour(255, 255, 255))  # 白色文字
        # 布局
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        file_sizer = wx.BoxSizer(wx.HORIZONTAL)

        # 添加文件选择框和按钮到水平布局
        file_sizer.Add(self.config_file_display, 1, wx.EXPAND | wx.ALL, 5)
        file_sizer.Add(self.select_file_button, 0, wx.EXPAND | wx.ALL, 5)

        # 将文件选择部分和联系人列表及聊天区域添加到主布局
        main_sizer.Add(file_sizer, 0, wx.EXPAND)
        contacts_sizer = wx.BoxSizer(wx.HORIZONTAL)
        contacts_sizer.Add(self.contact_list, 1, wx.EXPAND | wx.ALL, 5)
        contacts_sizer.Add(self.chat_display, 2, wx.EXPAND | wx.ALL, 5)
        main_sizer.Add(contacts_sizer, 1, wx.EXPAND)

        # 顶部布局设置完成，设置主面板的布局
        panel.SetSizer(main_sizer)

        # 底部控件布局
        bottom_sizer = wx.BoxSizer(wx.HORIZONTAL)

        # 添加导出按钮
        self.export_button = wx.Button(panel, label='导出')
        self.export_button.SetBackgroundColour(wx.Colour(50, 50, 50))  # 黑色背景
        self.export_button.SetForegroundColour(wx.Colour(255, 255, 255))  # 白色文字

        bottom_sizer.Add(self.export_button, 0, wx.EXPAND | wx.ALL, 5)
        self.export_button.Bind(wx.EVT_BUTTON, self.on_export)

        # 添加退出按钮
        self.exit_button = wx.Button(panel, label='退出')
        bottom_sizer.Add(self.exit_button, 0, wx.EXPAND | wx.ALL, 5)
        self.exit_button.Bind(wx.EVT_BUTTON, self.on_exit)
        self.exit_button.SetBackgroundColour(wx.Colour(50, 50, 50))  # 黑色背景
        self.exit_button.SetForegroundColour(wx.Colour(255, 255, 255))  # 白色文字

        # 添加检索输入框和检索按钮
        self.search_box = wx.TextCtrl(panel)
        bottom_sizer.Add(self.search_box, 1, wx.EXPAND | wx.ALL, 5)
        self.search_box.SetBackgroundColour(wx.Colour(50, 50, 50))  # 黑色背景
        self.search_box.SetForegroundColour(wx.Colour(255, 255, 255))  # 白色文字

        self.search_button = wx.Button(panel, label='检索')
        bottom_sizer.Add(self.search_button, 0, wx.EXPAND | wx.ALL, 5)
        self.search_button.Bind(wx.EVT_BUTTON, self.on_search)
        self.search_button.SetBackgroundColour(wx.Colour(50, 50, 50))  # 黑色背景
        self.search_button.SetForegroundColour(wx.Colour(255, 255, 255))  # 白色文字

        # 将底部布局添加到主布局
        main_sizer.Add(bottom_sizer, 0, wx.EXPAND)

        #self.SetSizer(main_sizer)

    def on_select_file(self, event):
        global rdb
        global wpath
        global wxidlist
        # 选择微信目录，就是那个MD5数字的目录
        #with wx.FileDialog(self, "选择 .lst 文件", wildcard="*.lst",
                          # style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as file_dialog:
        with wx.DirDialog(self, "选择目录") as file_dialog:  # 使用上下文管理器自动处理销毁
            #if file_dialog.ShowModal() == wx.ID_OK:
                #selected_file_path = file_dialog.GetPath()
                #self.config_file_display.SetValue(selected_file_path)  # 显示选择的文件路径
                #self.read_config(selected_file_path)  # 读取配置文件
            if file_dialog.ShowModal() == wx.ID_OK:
                # 获取用户选择的目录路径
                selected_file_path = file_dialog.GetPath()
                # 在文本框中显示当前选择的目录
                self.config_file_display.SetValue(selected_file_path)  # 显示选择的文件路径
                # 获取最后一级目录的路径名
                weixin_md5 = os.path.basename(selected_file_path)
                print(selected_file_path)
                print("weixin_md5:",weixin_md5)
                # 读取联系人列表
                # iOS版微信，基本上所有的 好友 和 聊天 数据都是存放在DB这个文件夹。
                # 好友：WCDB_Contact.sqlite
                # 聊天：MM.sqlite
                #Friend 这个表
                rdb = selected_file_path + "/DB/"
                wpath = selected_file_path + "/"
                # 获取微信ID列表
                wxidlist = self.weixinidinfo()

                db_path = selected_file_path + "/DB/WCDB_Contact.sqlite"
                self.load_contacts(db_path)

    def on_exit(self, event):
        self.Close()

    def on_export(self, event):
        # 导出选中的聊天数据，生成html文件及各种文件
        #wx.MessageBox('导出功能尚未实现', '提示', wx.OK | wx.ICON_INFORMATION)
        global rdb, wpath, wxidlist, wxgzh  # 数据库路径、媒体文件路径、微信ID列表、公众号列表

        # 打开目录对话框，选择保存路径
        dialog = wx.DirDialog(None, "选择保存目录:", style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON)
        if dialog.ShowModal() == wx.ID_OK:
            save_path = dialog.GetPath()
        else:
            self.OnCloseMe('没有选择保存路径！')  # 提示用户未选择保存路径
            return
        dialog.Destroy()
        #取得选中的公众号，聊天id
        selected_gzh = self.contact_list.GetStringSelection()
        if not selected_gzh:
            self.OnCloseMe('请先选择左边公众号！')  # 提示用户未选择公众号
            #sys.exit()  # 退出当前程序，但不重启shell
            return
        # 这里可以添加代码，以根据选中的联系人读取聊天记录并显示

        # 以双引号字符进行分割,处理选中的公众号信息
        parts = selected_gzh.split('###')
        wechat_id = parts[0]
        wechat_name = parts[1]
        #print(wechat_id)
        wechat_md5 = hashlib.md5(wechat_id.encode('utf-8')).hexdigest()  # 计算用户名的 MD5 哈希值
        save_file_name = wechat_id  #selected_gzh
        # 示例用法
        #s = "这是一个包含  不可见字符 的字符串\t\n"
        #print(remove_invisible_chars(s))  # 输出: 这是一个包含不可见字符的字符串
        save_file_name = self.remove_invisible_chars(save_file_name)
        #wechat_name
        # 生成聊天记录表
        chat_table_name = 'Chat_' + wechat_md5
        #print(chat_table_name)
        # hu获取数据库多个文件
        mfiles_lists = self.find_message_files(rdb)
        db_if = 0
        db_file = ""
        for filename in mfiles_lists:
            db_path = rdb + filename  # 'MM.sqlite'
            # 连接数据库并读取联系人列表
            connection = sqlite3.connect(db_path)
            cursor = connection.cursor()

            # 查询 sqlite_master 表以检查表是否存在
            cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?;", (chat_table_name,))
            result = cursor.fetchone()
            if result is not None:
                #print(f"表 '{table_name}' 存在。")
                db_if = 1
                db_file = db_path
                break

        if db_if == 0:
            print(wechat_id, wechat_name, "没有聊天记录！")
            self.chat_display.Clear()
            # self.chat_display.SetLabel("")
            return

        print(wechat_id, wechat_name, "导出聊天记录！")
        try:
            #读取聊天记录，显示
            # 执行查询操作
            connection = sqlite3.connect(db_file)
            # 设置游标的行工厂为sqlite3.Row，以便使用字段名访问
            # connection.row_factory = sqlite3.Row
            cursor = connection.cursor()
            query = f"SELECT CreateTime, MesLocalID, Type, Message, Status,MesSvrID FROM {chat_table_name} ORDER BY CreateTime ASC"
            cursor.execute(query)
            chat_records = cursor.fetchall()
            # conn.close()  # 关闭数据库连接

            # 创建图片目录
            img_path = os.path.join(save_path, f"{save_file_name}_img")
            os.makedirs(img_path, exist_ok=True)  # 如果目录不存在则创建
            # 创建并写入HTML文件
            with open(os.path.join(save_path, f"{save_file_name}.html"), 'w', encoding='UTF-8', newline='') as fout:
                fout.write('<html><head><meta charset="UTF-8"/></head><body><table>')

                for index, record in enumerate(chat_records):
                    # print(record)
                    print("#", end='')  # 以不换行的方式打印“#”，打印进度条
                    if index % 150 == 0 and index > 0:
                        print(index)
                    self.save_message(wechat_id, wechat_name, wechat_md5, record,img_path,fout)
                print()

                fout.write('</table></body></html>')  # 结束HTML文件
                self.OnCloseMe('聊天记录导出完成！')  # 导出完成的提示
        except sqlite3.Error as e:
            wx.MessageBox(f'数据库读取失败: {e}', '错误', wx.OK | wx.ICON_ERROR)
        finally:
            # 确保数据库连接在结束时关闭
            if connection:
                connection.close()

        #conn.close()  # 关闭数据库连接
        # 处理opendata目录，看是否有数据，有的话，拷贝到_img目录下的opendata目录保存
        # 创建目录
        opendata_path = os.path.join(save_path, f"{save_file_name}_img/OpenData")
        os.makedirs(opendata_path, exist_ok=True)  # 如果目录不存在则创建
        # 原目录 wpath
        opendata_org_path = os.path.join(wpath, f"OpenData",wechat_md5)
        #print(opendata_path)
        #print(opendata_org_path)
        # 检查目录是否存在
        if os.path.exists(opendata_org_path) and os.path.isdir(opendata_org_path):
            #print(f"目录 '{opendata_org_path}' 存在，以下是该目录下的所有文件：")
            # 列出目录下的所有文件
            for file_name in os.listdir(opendata_org_path):
                file_path = os.path.join(opendata_org_path, file_name)
                if os.path.isfile(file_path):  # 检查是否是文件
                    #print(file_name)
                    full_org_path3 = f"{opendata_org_path}/{file_name}"  # 完整图片文件路径
                    full_destination_path3 = os.path.join(opendata_path, f"{file_name}")  # 目标路径

                    # 拷贝完整图片文件，如果存在
                    if os.access(full_org_path3, os.F_OK):
                        shutil.copyfile(full_org_path3, full_destination_path3)
                    if file_path.lower().endswith(('.pic_thum', '.pic_hd', '.video_thum')):
                        full_destination_path3 = full_destination_path3 + ".pic"
                        shutil.copyfile(full_org_path3, full_destination_path3)
        else:
            print(f"目录 '{opendata_org_path}' 不存在!。")
        event.Skip()  # 继续事件处理

    # 过滤字符串中不可见字符
    def remove_invisible_chars(self,s):
        # 使用正则表达式匹配所有不可见字符，并将其替换为空字符串
        return re.sub(r'\s', '', s)

    def save_message(self, wechat_id, wechat_name, wechat_md5, record,img_path,fout):
        # 导出一条记录
        timestamp, message_id, message_type, message_content, status,messvrid = record
        formatted_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp))  # 格式化时间

        username, message_body, logo_path = self.get_user_info(message_content)

        message_html = self.construct_message(message_type, username, message_body, message_id, wechat_md5,img_path)

        # 检查消息状态
        if status == 3:
            # 自己的发出信息
            message_html = f'<font color="orange">{message_html}</font>'  # 如果消息状态为3，字体颜色为蓝色

        fout.write(f'<tr><td>{formatted_time}</td><td>{message_html}</td><td></td></tr>')

    # save 导出各种类型数据
    def construct_message(self, mtype, username, msg_content, message_id, gzh_md5, img_path):
        global rdb
        global wpath

        msg_content = msg_content.strip()
        """构造消息内容并处理多媒体类型"""
        media_map = {
            3: 'Img',  # 图片
            34: 'Audio',
            43: 'Video'
        }

        output_message = ''
        if mtype in media_map:
            msg = f"{username}: {media_map[mtype]}下载：{wpath}/{media_map[mtype]}/{gzh_md5}/{message_id}.{self.get_extension(mtype)}"
            media_file_path = f"{wpath}/{media_map[mtype]}/{gzh_md5}/{message_id}.{self.get_extension(mtype)}"
            destination_file = os.path.join(img_path, f"{message_id}.{self.get_extension(mtype)}")
            imgv = destination_file
            # 如果媒体文件存在，则复制到目标路径
            if os.access(media_file_path, os.F_OK):
                shutil.copyfile(media_file_path, destination_file)
                # 把下面三种文件转换为可视文件
                if media_file_path.lower().endswith(('.pic_thum', '.pic_hd', '.video_thum')):
                    destination_file3 = destination_file + ".pic"
                    #print(media_file_path)
                    shutil.copyfile(media_file_path, destination_file3)
            # 针对图片，拷贝完整版
            if mtype == 3:  # 图片
                full_image_path = f"{wpath}/{media_map[mtype]}/{gzh_md5}/{message_id}.pic"  # 完整图片文件路径
                full_destination_path = os.path.join(img_path, f"{message_id}.pic")  # 目标路径

                # 拷贝完整图片文件，如果存在
                if os.access(full_image_path, os.F_OK):
                    shutil.copyfile(full_image_path, full_destination_path)
                    imgv = full_destination_path

                full_image_path3 = f"{wpath}/{media_map[mtype]}/{gzh_md5}/{message_id}.pic_hd"  # 完整图片文件路径
                full_destination_path3 = os.path.join(img_path, f"{message_id}.pic_hd")  # 目标路径
                # 拷贝完整图片文件，如果存在
                if os.access(full_image_path3, os.F_OK):
                    shutil.copyfile(full_image_path3, full_destination_path3)
                    # 把下面三种文件转换为可视文件
                    if full_image_path3.lower().endswith(('.pic_thum', '.pic_hd', '.video_thum')):
                        destination_file4 = full_destination_path3 + ".pic"
                        #print(media_file_path)
                        shutil.copyfile(full_image_path3, destination_file4)

                output_message = f"{username}: <br /><img style=\"-webkit-user-select: none;margin: auto;\" src='{imgv}' height=\"400\" width=\"300\" > 或 <a href='{imgv}'>{media_map[mtype]}显示</a>"
            if mtype == 34:  # Audio
                output_message = f"{msg} 或 <a href='{imgv}'>{media_map[mtype]}显示</a>"

            if mtype == 43:  # Video
                full_image_path = f"{wpath}/{media_map[mtype]}/{gzh_md5}/{message_id}.mp4"  # 完整图片文件路径
                full_destination_path = os.path.join(img_path, f"{message_id}.mp4")  # 目标路径

                # 拷贝完整图片文件，如果存在
                if os.access(full_image_path, os.F_OK):
                    shutil.copyfile(full_image_path, full_destination_path)
                    imgv = full_destination_path

                output_message = f"{msg} 或 <a href='{imgv}'>{media_map[mtype]}显示</a>"
        elif mtype == 42:
            # 名片信息
            output_message = self.process_card_message(username, msg_content)
        elif mtype in {47}:
            # 47 大表情图,
            #output_message = self.process_special_messages(mtype, username, msg_content)
            emo_list = self.process_emoji_message(username, msg_content)
            output_message = f'表情符，从 {emo_list[1]}，发给 {emo_list[2]}'
            if  emo_list[3] != "":
                img_url = f'<img src="{emo_list[4]}" >'
                output_message = output_message  + f'表情名：{emo_list[3]}<br />' + img_url
        elif mtype == 48:
            # 位置消息
            output_message = self.process_gps_message(username, msg_content)
        elif mtype in {49}:
            # 49 表情图,
            output_message = self.process_special_messages(mtype, username, msg_content)
            output_message2 = self.save_app_message(username, msg_content)
            # 将元组转换为字符串
            #output_message2_str = " ".join(output_message2)
            if output_message2 != "":
                #output_message = output_message + "<br />" + output_message2
                output_message =  output_message2
            #print(output_message)
        elif mtype == 50:
            #output_message = '视频/语音通话'
            output_message = self.process_VoIP_message(username, msg_content)
        elif mtype in {10002}:
            # 系统信息
            output_message = self.process_message10002(username, msg_content, mtype)
        else:
            output_message = f"{username}: {msg_content}"

        return output_message

    def save_app_message(self, username, message_body):
        # 49
        # Implementation for processing application messages...
        #print(message_body)
        # 解析 XML
        r_string = ""
        message_body = message_body.strip()
        try:
            root = ET.fromstring(message_body)

            # 调用函数判断是否包含emoji,# 查找是否有<title>标签
            # 查找<title>标签
            title_elements = root.findall('.//title')  # 使用XPath查找<title>标签
            if title_elements:
                #print("消息中包含emoji")
                # 提取 title 和 des
                #title = root.find('.//title').text
                title_value = title_elements[0].text  # 获取第一个<title>标签的文本内容

                description_elements = root.findall('.//des')
                if description_elements:
                    #description = root.find('.//des').text
                    description = description_elements[0].text
                else:
                    description = ""

                url_elements = root.findall('.//url')
                if url_elements:
                    #description = root.find('.//des').text
                    url_value = url_elements[0].text
                else:
                    url_value = ""
                    #url = root.find('.//url').text

                # 输出结果
                #print(f'Title: {title_value}')
                #print(f'Description: {description}')
                # 链接：{url_value}
                #r_string = f'{username}: 标题：<a href = "{url_value}" > {title_value} < /a> <br /> 摘要：{description} <br />'
                r_string = username + f'<br />标题：<a href = "{url_value}" > {title_value} < /a> <br /> 摘要：{description} <br />'

                title_value = title_value if title_value is not None else None  # 检查 title 元素是否存在

                # 判断 title 是否包含特定字符串
                if title_value and "当前版本不支持展示该内容" in title_value:  # 进行 None 检查
                    # 提取 finderFeed 下的 nickname, avatar, desc
                    finder_feed = root.find('.//finderFeed')
                    if finder_feed is not None:  # 检查 finder_feed 是否为 None
                        nickname = finder_feed.find('nickname').text
                        avatar = finder_feed.find('avatar').text
                        desc = finder_feed.find('desc').text
                        #print(nickname,avatar,desc)
                        img_url = f'<img src="{avatar}" >'
                        r_string2 = f'<br />{nickname}: <br />图片：{img_url} <br />摘要：{desc} <br />'
                        r_string = r_string + r_string2
                    else:
                        #print(message_body)
                        #print("找不到 finderFeed 元素")
                        finder_feed = root.find('.//finderLive')
                        if finder_feed is not None:  # 检查 finder_feed 是否为 None
                            nickname = finder_feed.find('nickname').text
                            avatar = finder_feed.find('headUrl').text
                            desc = finder_feed.find('desc').text
                            #print(nickname,avatar,desc)
                            img_url = f'<img src="{avatar}" >'
                            r_string2 = f'<br />{nickname}: <br />图片：{img_url} <br />摘要：{desc} <br />'
                            r_string = r_string + r_string2
                        else:
                            # print(message_body)
                            #print("找不到 finderFeed 元素")
                            x1 =1
            else:
                #print("消息中不包含emoji")
                emoji_elements = root.findall('emoji')

                if len(emoji_elements) > 0:
                    # 查找<emoji>标签
                    for emoji in emoji_elements:
                        fromusername = emoji.get('fromusername')
                        tousername = emoji.get('tousername')
                        thumburl = emoji.get('thumburl')
                        #print(f"fromusername: {fromusername}, tousername: {tousername}, thumburl: {thumburl}")
                    r_string = " " + f'从 {fromusername}: 到：{tousername} <br /> 链接：{thumburl}'

        except ET.ParseError as e:
            x = 1
            #print(message_body)
            #print(f'XML解析错误: {e}')
        #except Exception as e:
        #    print(message_body)
        #    print(f'发生错误: {e}')

        #pass
        return r_string

    def get_extension(self, mtype):
        """根据消息类型获取文件扩展名"""
        return {
            3: 'pic_thum',
            34: 'aud',
            43: 'video_thum',
        }.get(mtype, '')

    def process_special_messages(self, mtype, username, msg_content):
        """处理特定消息类型的函数"""

        return f"{username}: 特殊消息处理: {msg_content}"

    def OnCloseMe(self,msg):
        dlg = wx.MessageDialog(None, u" " + msg, u"标题信息", wx.YES_NO | wx.ICON_QUESTION)
        if dlg.ShowModal() == wx.ID_YES:
            # self.Close(True)
            aa = ''
        dlg.Destroy()

    def on_search(self, event):
        search_term = self.search_box.GetValue()
        if not search_term:
            # 如果输入框为空，提示并退出
            self.OnCloseMe('请输入选择条件！')
            return
        #wx.MessageBox(f'检索功能尚未实现。检索词: {search_term}', '提示', wx.OK | wx.ICON_INFORMATION)
        # 取得选中的公众号，聊天id
        selected_gzh = self.contact_list.GetStringSelection()
        if not selected_gzh:
            self.OnCloseMe('请先选择左边公众号！')  # 提示用户未选择公众号
            # sys.exit()  # 退出当前程序，但不重启shell
            return
        # 这里可以添加代码，以根据选中的联系人读取聊天记录并显示

        # 以双引号字符进行分割,处理选中的公众号信息
        parts = selected_gzh.split('###')
        wechat_id = parts[0]
        wechat_name = parts[1]
        # print(wechat_id)
        wechat_md5 = hashlib.md5(wechat_id.encode('utf-8')).hexdigest()  # 计算用户名的 MD5 哈希值
        save_file_name = wechat_name
        # 生成聊天记录表
        chat_table_name = 'Chat_' + wechat_md5
        # print(chat_table_name)
        # hu获取数据库多个文件
        mfiles_lists = self.find_message_files(rdb)
        db_if = 0
        db_file = ""
        for filename in mfiles_lists:
            db_path = rdb + filename  # 'MM.sqlite'
            # 连接数据库并读取联系人列表
            connection = sqlite3.connect(db_path)
            cursor = connection.cursor()

            # 查询 sqlite_master 表以检查表是否存在
            cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?;", (chat_table_name,))
            result = cursor.fetchone()
            if result is not None:
                #print(f"表 '{table_name}' 存在。")
                db_if = 1
                db_file = db_path
                break

        if db_if == 0:
            print(wechat_id, wechat_name, "没有聊天记录！")
            self.chat_display.Clear()
            # self.chat_display.SetLabel("")
            return
        print(wechat_id, wechat_name, "查看聊天记录！")
        try:
            #读取聊天记录，显示
            # 执行查询操作
            connection = sqlite3.connect(db_file)
            # 设置游标的行工厂为sqlite3.Row，以便使用字段名访问
            # connection.row_factory = sqlite3.Row
            cursor = connection.cursor()
            query = f"SELECT CreateTime, MesLocalID, Type, Message, Status,MesSvrID FROM {chat_table_name} WHERE Message LIKE '%{search_term}%' ORDER BY CreateTime ASC"
            cursor.execute(query)
            chat_records = cursor.fetchall()
            #conn.close()  # 关闭数据库连接

            # 清空聊天记录显示框
            self.chat_display.Clear()

            #for record in chat_records:
            for index, record in enumerate(chat_records):
                #print(record)
                print("#", end='')  # 以不换行的方式打印“#”，打印进度条
                if index % 150 == 0 and index > 0:
                    print(index)

                db_if = 1
                self.display_message(wechat_id,wechat_name,wechat_md5,record,db_if)
            print()
        except sqlite3.Error as e:
            wx.MessageBox(f'数据库读取失败: {e}', '错误', wx.OK | wx.ICON_ERROR)
        finally:
            # 确保数据库连接在结束时关闭
            if connection:
                connection.close()

    def read_config(self, selected_file_path):
        # 新的环境已经没有这个配置文件了
        global rdb
        global wpath
        global wxidlist
        """读取LocalInfo.lst文件中的微信号和昵称"""
        try:
            with open(selected_file_path, 'rb') as f:
                plist = plistlib.load(f)  # 使用 plistlib.load 来读取 plist 文件
                objects = plist['$objects']
                weixin_id = objects[3].encode('UTF-8')
                weixin_name = objects[4]
                weixin_phone = objects[11]
                #weixin_id = weixin_id.decode('utf-8')

            # 生成微信ID的MD5值,就是对应的目录名
            weixin_md5 = hashlib.md5(weixin_id).hexdigest()
            print(f"微信号: {weixin_id}, 昵称: {weixin_name}, MD5: {weixin_md5},phone num:{weixin_phone}")

            #打开另一个配置文件 mmsetting.archive
            # 拆分为目录和文件名
            directory, file_name = os.path.split(selected_file_path)
            file_path2 = directory + "/" + weixin_md5 + "/mmsetting.archive"
            with open(file_path2, 'rb') as f:
                plist = plistlib.load(f)  # 使用 plistlib.load 来读取 plist 文件
                objects = plist['$objects']
                weixin_qq = objects[4]
                weixin_phone2 = objects[5]
                #w10 = objects[10]
                w49 = objects[49] # 微信头像
                w52 = objects[52] # 微信头像缩图，小图

            print(weixin_qq,weixin_phone2)
            #print(w10)
            #print(w49)
            #print(w52)
            # 读取联系人列表
            # iOS版微信，基本上所有的 好友 和 聊天 数据都是存放在DB这个文件夹。
            # 好友：WCDB_Contact.sqlite
            # 聊天：MM.sqlite
            #Friend 这个表
            rdb = directory + "/" + weixin_md5 + "/DB/"
            wpath = directory + "/" + weixin_md5 + "/"
            # 获取微信ID列表
            wxidlist = self.weixinidinfo()

            db_path = directory + "/" + weixin_md5 + "/DB/WCDB_Contact.sqlite"
            self.load_contacts(db_path)
        except Exception as e:
            wx.MessageBox(f'读取配置文件失败: {e}', '错误', wx.OK | wx.ICON_ERROR)

    def load_contacts(self, db_path):
        """根据微信ID的MD5值读取联系人数据库"""
        if not os.path.exists(db_path):
            wx.MessageBox('联系人数据库文件未找到', '错误', wx.OK | wx.ICON_ERROR)
            return

        # 连接数据库并读取联系人列表
        try:
            connection = sqlite3.connect(db_path)
            # 设置游标的行工厂为sqlite3.Row，以便使用字段名访问
            connection.row_factory = sqlite3.Row
            cursor = connection.cursor()
            # where type not in (0,1,4,6)
            cursor.execute("SELECT * FROM Friend where type not in (0,4,6) order by userName")  # 假设联系人表为Contacts、昵称字段为NickName
            contacts = cursor.fetchall()

            self.contact_list.Clear()
            for contact in contacts:
                username = contact['userName']  # 获取用户名,weixin_id
                username_hash = hashlib.md5(username.encode('utf-8')).hexdigest()  # 计算用户名的 MD5 哈希值
                weixin_name2 = self.clean_and_decode(contact['dbContactRemark'])  # 获取并清洗联系人名称 dbContactRemark
                #print(weixin_name2)
                weixin_name = weixin_name2
                logo_image_url = self.extract_logo_img(contact['dbContactHeadImage'])  # 获取头像 URL dbContactHeadImage
                usertype = contact['type']
                self.contact_list.Append(username + '###' + weixin_name) # 微信号ID
                #print(contact[1],usertype,username,weixin_name,username_hash,logo_image_url)
                #print( usertype, username, weixin_name, username_hash)
                #print(contact[7])
                #print(contact[8]) # 输出一行记录

            #connection.close()
        except sqlite3.Error as e:
            wx.MessageBox(f'数据库读取失败: {e}', '错误', wx.OK | wx.ICON_ERROR)
        finally:
            # 确保数据库连接在结束时关闭
            if connection:
                connection.close()

    def extract_logo_img(self,db_contact_head_image):
        # 按字节循环取值，二级制文件，按字节取值
        begin = 0
        end = 0
        byte_values = []
        i_num =0
        bytenum = len(db_contact_head_image)
        for i in range(0, bytenum):
            byte = db_contact_head_image[i]
            #print(byte,end=' ')
            if byte == 104 and begin == 0:  # 104 = h,结束 = 26
                byte_values.append(byte)
                begin = 1
                i_num = i_num + 1
            else:
                if begin > 0 and byte != 26:
                    if byte >= 32: #可见ASCii字符
                        byte_values.append(byte)
                    i_num = i_num + 1
                else:
                    if end == 0 and byte == 26:
                        end =  1
                        i_num = i_num + 1
                        break
                    else:
                        i_num = i_num + 1

            #print(byte,end=' ')  # 打印每个字节的整数值
        #print()
        #print(i)
        #print(i_num)
        # 第一个http logo小图
        byte_data = bytes(byte_values)
        result_string = byte_data.decode('utf-8', errors='ignore')  # 使用 'ignore' 忽略无法解码的字节
        #print(result_string)

        # 取第二个http
        begin = 0
        end = 0
        byte_values = []
        i_num2 =0
        for i in range(i_num, bytenum):
            byte = db_contact_head_image[i]
            #print(byte, end=' ')
            if byte == 104 and begin == 0:  # 104 = h,结束 = 26
                byte_values.append(byte)
                begin = 1
                i_num2 = i_num2 + 1
            else:
                if begin > 0 and (byte != 26 and byte != 34):
                    if byte >= 32:  # 可见ASCii字符
                        byte_values.append(byte)
                    i_num2 = i_num2 + 1
                else:
                    if end == 0 and (byte == 26 or byte == 34):
                        end = 1
                        i_num2 = i_num2 + 1
                        break
                    else:
                        i_num = i_num + 1

        #print()
        # logo 大图
        byte_data = bytes(byte_values)
        result_string2 = byte_data.decode('utf-8', errors='ignore')  # 使用 'ignore' 忽略无法解码的字节
        #print(result_string2)


        """提取并返回头像的 URL"""
        #html_content = str(db_contact_head_image).strip().split('\t')[0]  # 取第一个元素

        # 替换 URL 前缀
        #html_content = html_content.replace("https://", " https://").replace("http://", " http://")
        #unique_urls = self.extract_unique_urls(html_content)
        # print('2--',unique_urls2)
        result_string
        #return unique_urls[-1].replace("'", "") if unique_urls else ''  # 返回最后一个 URL 或者空字符串

        return result_string if result_string else ''  # 返回第一个 URL 或者空字符串

    def extract_unique_urls(self,html_content):
        # 定义匹配 URL 的正则表达式
        url_pattern = r'https?://[^\s"<>]+'  # 匹配 http 和 https 开头的 URL
        # 使用 re.findall 提取所有 URL
        urls = re.findall(url_pattern, html_content)
        # 使用 set 保证唯一性
        if urls:
            unique_urls = urls  # set(urls)
        else:
            unique_urls = urls
        return unique_urls

    def clean_and_decode(self,db_contact_remark):
        # blob 二级制文件 按字节循环取值,第一位不要，第二位是指后面连续取几个字节
        z_len= len(db_contact_remark)
        #print("++",z_len)
        #bytenum=db_contact_remark[1]
        #print(bytenum)
        j = 0
        byte_valuesz = []
        while  j < z_len :
            # 创建一个空列表
            byte_values = []
            bytenum = db_contact_remark[j+1]

            if bytenum>0 :
                for i in range(1, bytenum+1):
                    byte_values.append(db_contact_remark[j+i+1])  # 将 i 添加到列表中
                    #print(i, end=' ')  # 在同一行打印数字，空格分隔
            if bytenum >0 :
                byte_valuesz.append(byte_values)
                #print(bytenum)
                #print("=",byte_values)
            else:
                byte_valuesz.append([32])
            #print("=", byte_values)
            j = j + bytenum + 2
        result_string = ""
        i1 = 1
        # 目前每个人共八项
        for row in byte_valuesz:
            # 将字节值列表转换为字节对象
            byte_data = bytes(row)
            #print(i1,byte_data.decode('utf-8', errors='ignore') )
            if i1 <=3 and len(byte_data)>2:
                # 将字节对象解码为字符串
                #print(i1)
                result_string = result_string  + " " + byte_data.decode('utf-8', errors='ignore')  # 使用 'ignore' 忽略无法解码的字节
                i1 = i1 +1

        return result_string

    def get_contact_db_path(self, weixin_md5):
        """根据MD5值生成联系人数据库路径，这里是示例逻辑"""
        # 这里需要根据实际情况设置路径
        return f"/path/to/weixin/db/{weixin_md5}_contacts.db"  # 修改为正确的文件路径

    # 显示一行聊天记录
    def display_message(self, wechat_id,wechat_name,wechat_md5,record,db_if):
        global wpath

        # db_if = 2 是公众号表
        create_time, mes_local_id, message_type, message_content, message_status,messvrid = record
        if db_if == 2 and isinstance(message_content, bytes):
            # 数据是加密的，不知道如何解密
            #print(message_content)
            z_len = len(message_content)
            j = 0
            #print(message_content)
            byte_valuesz = []
            while j < z_len:
                # 创建一个空列表
                # byte_values = []
                bytenum = message_content[j]
                if bytenum >= 32:
                    byte_valuesz.append(bytenum)  # 将 i 添加到列表中
                j = j + 1
            byte_data = bytes(byte_valuesz)
            #print(byte_valuesz)
            message_content = byte_data.decode('utf-8', errors='ignore')
            #unzipStr = lb.decompress(message_content, uncompressed_size=0x40000)
            #message_content = unzipStr.decode('utf-8')
            #print(message_content)

            # 看是不是订阅号
            ghdb_path = wpath + "brandTimelineMsg/message.db"
            conn = sqlite3.connect(ghdb_path)  # 连接 SQLite 数据库
            # 设置游标的行工厂为sqlite3.Row，以便使用字段名访问
            #conn.row_factory = sqlite3.Row
            cursor = conn.cursor()  # 创建游标对象

            sql_query = f'SELECT createTime,userName,type,message,msgServerId FROM BrandTimeline where userName="{wechat_id}" and msgServerId="{messvrid}"'  # SQL 查询语句
            #print(sql_query)
            cursor.execute(sql_query)  # 执行 SQL 查询
            rows = cursor.fetchall()  # 获取所有查询结果
            conn.close()
            #if rows is not None:
            for row in rows:
                bcreate_time, buserName, btype, bmessage_content,  messvrid = row
                message_content = bmessage_content

        # 将时间戳转换为可读时间格式
        formatted_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(create_time))

        username, message_body, logo_path = self.get_user_info(message_content)
        # print(message_content)
        message_str = self.get_message_string(message_type, username, message_body, mes_local_id,wechat_id,wechat_name,wechat_md5)
        # 最后检查字符串类型是否是byte类型
        #if isinstance(message_str, bytes):
        #    #message_str = message_str.decode('utf-8', errors='ignore')
        #    message_str = self.byte_to_string(message_str)
            #print(message_str)
        # 根据消息状态调整文本对齐和颜色
        self.adjust_text_formatting(message_status)
        # print(username,logo_path,message_status,message_str)

        # 显示消息内容 {message_type}
        self.chat_display.WriteText(f'{formatted_time} {message_type} {message_str}\n')
        self.show_image_if_exists(wechat_id,wechat_name,wechat_md5,message_type, mes_local_id)
        self.chat_display.EndTextColour()
        self.chat_display.EndAlignment()

    def byte_to_string(self, message_content):
        z_len = len(message_content)
        j = 0
        byte_valuesz = []
        while j < z_len:
            bytenum = message_content[j]
            if bytenum>= 32:
                byte_valuesz.append(bytenum)  # 将 i 添加到列表中
            j = j + 1
        byte_data = bytes(byte_valuesz)
        byte_string = byte_data.decode('utf-8', errors='ignore')
        return byte_string

    def get_user_info(self, message_content):
        global wxidlist
        message_parts = str(message_content).strip().split(':', 1)
        username = ''
        message_body = message_content
        #print(message_parts)
        #print('===',message_parts[0])

        # 对群聊有weixin_id,发话者
        for user in wxidlist:
            if message_parts[0] == user[1]:
                username = user[2]
                message_body = message_parts[1]
                break

        return username, message_body, ''

    # 获取微信联系人信息
    def weixinidinfo(self):
        global rdb  # 引入全局变量 rdb
        db_path = f"{rdb}/WCDB_Contact.sqlite"  # 拼接数据库路径
        # print(db_path)
        conn = sqlite3.connect(db_path)  # 连接 SQLite 数据库
        # 设置游标的行工厂为sqlite3.Row，以便使用字段名访问
        conn.row_factory = sqlite3.Row
        cursor = conn.cursor()  # 创建游标对象

        sql_query = "SELECT * FROM friend"  # SQL 查询语句
        cursor.execute(sql_query)  # 执行 SQL 查询
        rows = cursor.fetchall()  # 获取所有查询结果

        contact_list = []  # 存储联系人信息的列表
        # url_extractor = URLExtract()  # 创建 URL 提取器对象

        for row in rows:  # 遍历每一行数据
            #print(row)
            weichat_id = row['userName']  # 获取用户名
            user_type = row['type']
            #print(weichat_id)
            username_hash = hashlib.md5(weichat_id.encode('utf-8')).hexdigest()  # 计算用户名的 MD5 哈希值
            weixin_name2 = self.clean_and_decode(row['dbContactRemark'])  # 获取并清洗联系人名称
            logo_image_url = self.extract_logo_img(row['dbContactHeadImage'])  # 获取头像 URL
            weixin_name = weixin_name2
            if weixin_name:  # 如果 weixin_name 不为空
                contact_list.append([username_hash, weichat_id, weixin_name, logo_image_url,user_type])  # 添加到联系人列表

        conn.close()  # 关闭数据库连接
        return contact_list  # 返回所有联系人的信息列表

    # 处理一条聊天信息
    def get_message_string(self,message_type, username, message_body, mes_local_id, wechat_id,wechat_name,wechat_md5):
        global wpath
        # print(message_type,username,message_body)
        #print("====",message_type)
        message_map = {
            3: f'{username}: 图片下载：{wpath}/Img/{wechat_md5}/{mes_local_id}.pic_thum .pic 或 .pic_hd 或 .pic_thum',
            34: f'{username}: 声音下载：{wpath}/Audio/{wechat_md5}/{mes_local_id}.aud',
            43: f'{username}: 视频下载：{wpath}/Video/{wechat_md5}/{mes_local_id}.video_thum 或 .mp4',
        }
        if message_type == 10002:
            #print(message_body)
            message_string = self.process_message10002(username, message_body, message_type)
            return message_string
        elif message_type == 42:
            # 名片信息
            message_string = self.process_card_message(username, message_body)
            return message_string
        elif message_type == 47:
            emo_list = self.process_emoji_message(username, message_body)
            message_string = emo_list[0]
            #print(message_string)
            return message_string
        elif message_type == 48:
            # 位置消息
            message_string = self.process_gps_message(username, message_body)
            return message_string
        elif message_type == 49:
            message_string = self.process_app_message(username, message_body)
            return message_string
        elif message_type == 50:
            message_string = self.process_VoIP_message(username, message_body)
            return message_string
        else:
            return message_map.get(message_type, f'{username}: {message_body}')

    def process_card_message(self, username, message_body):
        # 42,名片信息
        #print("42",message_body)
        message_body = message_body.strip()
        from_username = ""
        if message_body.strip()[0:4] != "<msg":
            #print("首字符是 <?xml version="1.0"?>")
            message_parts = str(message_body).strip().split(':', 1)
            message_body = message_parts[1]
            from_username= message_parts[0]
        # 解析XML内容
        root = ET.fromstring(message_body)

        # 提取所需的属性值
        username = root.attrib.get('username')
        nickname = root.attrib.get('nickname')
        fullpy = root.attrib.get('fullpy')
        province = root.attrib.get('province')
        city = root.attrib.get('city')
        region_code = root.attrib.get('regionCode')
        sex = root.attrib.get('sex')

        # 打印结果
        #print(f'username: {username}')
        #print(f'nickname: {nickname}')
        #print(f'fullpy: {fullpy}')
        #print(f'province: {province}')
        #print(f'city: {city}')
        #print(f'regionCode: {region_code}')
        #print(f'sex: {sex}')
        if sex == "1":
            sex_name = "男"
        else:
            sex_name = "女"
        return_string= f'名片信息 微信号：{username} ，昵称：{nickname} ，全拼音：{fullpy}，性别：{sex_name}，省：{province}，城市：{city}，地区代码：{region_code}'
        #pass
        return return_string

    def process_gps_message(self, username, message_body):
        # 48 # 位置消息
        #print(f'48,{message_body}')
        message_body = message_body.strip()
        from_username = ""
        if message_body.strip()[0:1] != "<":
            #print("首字符是 <?xml version="1.0"?>")
            message_parts = str(message_body).strip().split(':', 1)
            message_body = message_parts[1]
            from_username= message_parts[0]
        # 解析XML
        root = ET.fromstring(message_body)
        # 找到location元素
        location = root.find('location')
        # 获取所需的属性值
        x = location.attrib.get('x', '')
        y = location.attrib.get('y', '')
        label = location.attrib.get('label', '')
        poiname = location.attrib.get('poiname', '')

        # 输出结果
        #print("x:", x)
        #print("y:", y)
        #print("label:", label)
        #print("poiname:", poiname)
        return_string= f'位置信息 坐标：x={x}，y={y} 地址：{label}，{poiname}'
        if from_username != "":
            return_string = f'{from_username}发送' + return_string
        #pass
        return return_string

    def process_emoji_message(self, username, message_body):
        global wpath
        # 47 大表情包
        # Implementation for processing emoji messages...
        message_body = message_body.strip()
        if message_body.strip()[0:5] != "<msg>":
            #print("首字符是 <msg>")
            message_parts = str(message_body).strip().split(':', 1)
            message_body = message_parts[1]
        #print(message_body)
        # 解析XML
        root = ET.fromstring(message_body)

        # 找到emoji元素
        emoji = root.find('emoji')

        # 获取所需的属性值
        productid = emoji.attrib.get('productid', '')
        fromusername = emoji.attrib.get('fromusername', '')
        tousername = emoji.attrib.get('tousername', '')

        #print("productid:", productid)
        #print("fromusername:", fromusername)
        #print("tousername:", tousername)
        db_path = f"{wpath}Emoticon/DB/Emoticon.sqlite"  # 拼接数据库路径
        #print(db_path)
        conn = sqlite3.connect(db_path)  # 连接 SQLite 数据库
        # 设置游标的行工厂为sqlite3.Row，以便使用字段名访问
        conn.row_factory = sqlite3.Row
        cursor = conn.cursor()  # 创建游标对象

        sql_query = f'SELECT * FROM EmoticonPackage05 where EmoticonPackageId = "{productid}"'  # SQL 查询语句
        #print(sql_query)
        cursor.execute(sql_query)  # 执行 SQL 查询
        rows = cursor.fetchall()  # 获取所有查询结果
        #print(rows)
        emo_list = []  # 存储联系人信息的列表
        # url_extractor = URLExtract()  # 创建 URL 提取器对象
        if len(rows) != 0:
            for row in rows:  # 遍历每一行数据
                #print(row)
                emo_iconUrl = row['EmoticonPackageIconUrl']  # 获取用户名
                emo_name = row['EmoticonPackageName']
                emo_filename = row['ConStrRes1'] #下载到本地的名字
                emo_panneUrl = row['EmoticonPackagePannelUrl']
                #print(f'{emo_name},\n {emo_panneUrl}{tousername}')
            return_string = f'表情符，从 {fromusername}，发给 {tousername}，表情名：{emo_name},\n 表情链接：\n {emo_panneUrl}'
            emo_list.append(return_string)
            emo_list.append(fromusername)
            emo_list.append(tousername)
            emo_list.append(emo_name)
            emo_list.append(emo_iconUrl)
            emo_list.append(emo_panneUrl)
        else:
            #print(message_body)
            return_string = f'表情符，从 {fromusername}，发给 {tousername}'
            emo_list.append(return_string)
            emo_list.append(fromusername)
            emo_list.append(tousername)
            emo_list.append("")
            emo_list.append("")
            emo_list.append("")
        conn.close()  # 关闭数据库连接

        #pass
        return  emo_list

    def process_VoIP_message(self, username, message_body):
        # 50 视频/语音通话
        # Implementation for processing emoji messages...
        # print(message_body)
        message_body = message_body.strip()
        msg_type=""
        msg_value =""
        try:
            # 解析XML
            root = ET.fromstring(message_body)

            # 获取type属性
            msg_type = root.attrib['type']

            # 获取msg值
            msg_value = root.find('.//msg').text

            #print("type值:", msg_type)
            #print("msg值:", msg_value)

        except ET.ParseError as e:
            #print("解析错误:", e)
            x1 = 1

        return_string = f'{username}: 视频/语音通话,type值: {msg_type},msg值: {msg_value}'
        #pass
        return return_string

    def process_app_message(self, username, message_body):
        # 49
        # Implementation for processing application messages...
        #print(message_body)
        # 查看title_element的类型
        #print("title_element的类型:", type(message_body))
        # title_element的类型: <class 'str'>
        # 解析 XML
        r_string = f'{username}: '
        message_body = message_body.strip()
        try:
            root = ET.fromstring(message_body)

            # 调用函数判断是否包含emoji,# 查找是否有<title>标签
            # 查找<title>标签
            title_elements = root.findall('.//title')  # 使用XPath查找<title>标签
            #title_element = root.find('.//title')
            # 检查<title>标签是否存在
            #if title_element is not None:
            #    # 获取其内容
            #    title = title_element.text
                # 输出提取的标题内容
            #    print(title)
            #else:
            #    print("未找到<title>标签")

            if title_elements:
                #print("消息中包含emoji")
                # 提取 title 和 des
                #title = root.find('.//title').text
                title_value = title_elements[0].text  # 获取第一个<title>标签的文本内容

                description_elements = root.findall('.//des')
                if description_elements:
                    #description = root.find('.//des').text
                    description = description_elements[0].text
                else:
                    description = ""

                url_elements = root.findall('.//url')
                if url_elements:
                    #description = root.find('.//des').text
                    url_value = url_elements[0].text
                    #url_value = "< a  href = '{url_value}' > 点击链接 < / a >"
                else:
                    url_value = ""
                    #url = root.find('.//url').text

                # 输出结果
                #print(f'Title: {title_value}')
                #print(f'Description: {description}')
                # 链接：{url}
                r_string = f'{username}: \n标题：{title_value} \n摘要：{description} \n链接：{url_value}\n'

                title_value = title_value if title_value is not None else None  # 检查 title 元素是否存在

                # 判断 title 是否包含特定字符串
                if title_value and "当前版本不支持展示该内容" in title_value:  # 进行 None 检查
                    # 提取 finderFeed 下的 nickname, avatar, desc
                    finder_feed = root.find('.//finderFeed')
                    if finder_feed is not None:  # 检查 finder_feed 是否为 None
                        nickname = finder_feed.find('nickname').text
                        avatar = finder_feed.find('avatar').text
                        desc = finder_feed.find('desc').text
                        #print(nickname,avatar,desc)
                        r_string2 = f'\n{nickname}: \n图片：{avatar} \n摘要：{desc} \n'
                        r_string = r_string + r_string2
                    else:
                        #print(message_body)
                        #print("找不到 finderFeed 元素")
                        finder_feed = root.find('.//finderLive')
                        if finder_feed is not None:  # 检查 finder_feed 是否为 None
                            nickname = finder_feed.find('nickname').text
                            avatar = finder_feed.find('headUrl').text
                            desc = finder_feed.find('desc').text
                            #print(nickname,avatar,desc)
                            r_string2 = f'\n{nickname}: \n图片：{avatar} \n摘要：{desc} \n'
                            r_string = r_string + r_string2
                        else:
                            # print(message_body)
                            #print("找不到 finderFeed 元素")
                            x1 =1
            else:
                #print("消息中不包含emoji")
                emoji_elements = root.findall('emoji')
                if len(emoji_elements) > 0:
                    # 查找<emoji>标签
                    for emoji in emoji_elements:
                        fromusername = emoji.get('fromusername')
                        tousername = emoji.get('tousername')
                        thumburl = emoji.get('thumburl')

                        #print(f"fromusername: {fromusername}, tousername: {tousername}, thumburl: {thumburl}")
                    r_string = f'从 {fromusername}: 到：{tousername} \n链接：{thumburl}\n'

        except ET.ParseError as e:
            x = 1
            #r_string = r_string + message_body
            #print(message_body)
            #print(f'XML解析错误: {e}')
        #except Exception as e:
        #    print(message_body)
        #    print(f'发生错误: {e}')

        #pass
        return r_string

    def process_message10002(self, username, message_body,message_type):
        # 信息类型1002 系统信息
        message_body = message_body.strip()
        #print("kkk===",message_type)
        #print(message_body)
        root = ET.fromstring(message_body)

        # 调用函数判断是否包含emoji,# 查找是否有<title>标签
        # 查找<title>标签
        # title_elements = root.findall('.//template')  # 使用XPath查找<title>标签
        title_element = root.find('.//template')
        #title = root.find('.//template').text
        # 检查<title>标签是否存在
        if title_element is not None:
            # 获取其内容
            title = title_element.text
            # 输出提取的标题内容
            # 第一步：去掉所有双引号
            title = title.replace('"', '')
            #print(title)
            # 检查子字符串是否存在
            if "加入了群聊" in title:
                #print(f"'{substring}' 存在于字符串中")
                username2 = "username"
                names = "names"
                title = self.get_username_names10002(username2, names, message_body,title)
            else:
                #print(f"'{substring}' 不存在于字符串中")
                if "修改群名为" in title:
                    username2 = "username"
                    names = "remark"
                    title =  self.get_username_names10002(username2, names, message_body,title)
                else:
                    # 原始信息输出
                    x1=1
        else:
            # 原始信息输出
            #print("未找到<title>标签")
            title = message_body

        r_string = f'{username}: \n {title} \n'
        return r_string

    def get_username_names10002(self,username2, names2, message_body,title):
        # 解析XML
        message_body = message_body.strip()
        root = ET.fromstring(message_body)

        # 提取 username 和 names
        user_info = {}
        # 遍历所有的 <link> 元素
        for link in root.findall('.//link'):
            link_name = link.get('name')
            member = link.find('.//member')

            # 确保 <member> 存在
            if member is not None:
                username = member.find('username').text
                nickname = member.find('nickname').text
                # 根据 link_name 添加到 user_info 中
                if link_name == username2:
                    user_info['username'] = {
                        'username': username,
                        'nickname': nickname
                    }
                    # 替换字符串信息
                    # 使用正则表达式替换字符串
                    if username is None:
                        username = " "  # 提供一个默认值
                    x1 = " " + username + " " + nickname + " "
                    # 替换占位符
                    title = title.replace("$username$", x1)
                elif link_name == names2:
                    user_info['names'] = {
                        'username': username,
                        'nickname': nickname
                    }
                    # 使用正则表达式替换字符串
                    if username is None:
                        username = " "  # 提供一个默认值
                    x1 = " " + username + " " + nickname + " "
                    xname2 = f'${names2}$'
                    title = title.replace(xname2, x1)
        # 输出提取的内容
        #print("提取的用户信息:")
        #for name, info in user_info.items():
        #    print(f"{name}: 用户名 = {info['username']}, 昵称 = {info['nickname']}")

        #return user_info
        # print(title)
        return title

    def adjust_text_formatting(self, message_status):
        #print(message_status)
        if message_status == 3:
            # 靠右边，橙色文字显示
            self.chat_display.BeginAlignment(wx.TEXT_ALIGNMENT_RIGHT)
            self.chat_display.BeginTextColour((235,92,92))
        else:
            self.chat_display.BeginAlignment(wx.TEXT_ALIGNMENT_LEFT)
            self.chat_display.BeginTextColour((255, 255, 255))

    def show_image_if_exists(self, wechat_id,wechat_name,wechat_md5,message_type, mes_local_id):
        if message_type in (3, 43):  # 图片或视频消息
            img_path = self.get_image_path(wechat_id,wechat_name,wechat_md5,message_type, mes_local_id)
            if img_path:
                # 在输出文件名之前判断文件是否存在
                if os.path.exists(img_path):
                    wx_image = wx.Image(img_path, type=wx.BITMAP_TYPE_ANY)
                    self.chat_display.Newline()
                    self.chat_display.WriteImage(wx_image)
                    self.chat_display.Newline()
        # self.m_richText1.EndAlignment()

    def get_image_path(self, wechat_id,wechat_name,wechat_md5,message_type, mes_local_id):
        if message_type == 3:  # 图片
            return wpath + '/Img/' + wechat_md5 + '/' + str(mes_local_id) + '.pic_thum'
        elif message_type == 43:  # 视频
            return wpath + '/Video/' + wechat_md5 + '/' + str(mes_local_id) + '.video_thum'
        return ''

    def find_message_files(self,directory):
        # 遍历指定目录
        files_lists=[]
        for filename in os.listdir(directory):
            # 检查文件名是否以 'message_' 开头且扩展名为 '.sqlite'
            if filename.startswith('message_') and filename.endswith('.sqlite'):
                # 输出找到的文件名
                files_lists.append(filename)
                #print(filename)
        files_lists.append("MM.sqlite")
        return files_lists

    def chatroom_contact(self,wechat_id):
        global rdb  # 引入全局变量 rdb
        db_path = f"{rdb}/WCDB_Contact.sqlite"  # 拼接数据库路径
        # print(db_path)
        conn = sqlite3.connect(db_path)  # 连接 SQLite 数据库
        # 设置游标的行工厂为sqlite3.Row，以便使用字段名访问
        conn.row_factory = sqlite3.Row
        cursor = conn.cursor()  # 创建游标对象

        sql_query = f'SELECT * FROM friend where userName = "{wechat_id}"'  # SQL 查询语句
        #print(sql_query)
        cursor.execute(sql_query)  # 执行 SQL 查询
        rows = cursor.fetchall()  # 获取所有查询结果

        contact_list = []  # 存储联系人信息的列表
        # url_extractor = URLExtract()  # 创建 URL 提取器对象

        for row in rows:  # 遍历每一行数据
            # print(row)
            weichat_id = row['userName']  # 获取用户名
            user_type = row['type']
            # print(weichat_id)
            username_hash = hashlib.md5(weichat_id.encode('utf-8')).hexdigest()  # 计算用户名的 MD5 哈希值
            weixin_name = self.clean_and_decode(row['dbContactRemark'])  # 获取并清洗联系人名称
            logo_image_url = self.extract_logo_img(row['dbContactHeadImage'])  # 获取头像 URL
            weixin_Contacts = self.Contacts_and_decode(row['dbContactChatRoom'])  # 获取并清洗联系人名称

        conn.close()  # 关闭数据库连接
        return
    def Contacts_and_decode(self,db_contact_ChatRoom):
        # blob 二级制文件 按字节循环取值,第一位不要，第二位是指后面连续取几个字节
        z_len= len(db_contact_ChatRoom)
        #print("++",z_len)
        #bytenum=db_contact_remark[1]
        #print(db_contact_ChatRoom)

        j = 0
        byte_valuesz = []
        while  j < z_len :
            # 创建一个空列表
            #byte_values = []
            bytenum = db_contact_ChatRoom[j]
            if bytenum>=32:
                byte_valuesz.append(bytenum)  # 将 i 添加到列表中
            j = j +1
        byte_data = bytes(byte_valuesz)
        chroom_string = byte_data.decode('utf-8', errors='ignore')
        result_string = self.extract_room_data(chroom_string,"<RoomData>","</RoomData>")
        #print(result_string)
        if result_string is not None:
            # 解析XML内容
            result_string = result_string.strip()
            root = ET.fromstring(result_string)

            # 遍历每个<Member>节点
            print("群成员如下：")
            for member in root.findall('Member'):
                user_name = member.get('UserName')
                flag = member.find('Flag').text
                display_name = member.find('DisplayName').text if member.find('DisplayName') is not None else None

                #print(f'UserName: {user_name}, Flag: {flag}, DisplayName: {display_name}')
                print(f'UserName: {user_name}, DisplayName: {display_name}')

        #result_string2 = self.extract_room_data(chroom_string,"<group_notice_item type=\"18\">","</group_notice_item>")
        #print(result_string2)
        return result_string

    # 截取指定区间字符串
    def extract_room_data(self,text,sbegin,send):
        start_index = text.find(sbegin)
        if start_index == -1:
            return None  # 如果没有找到<RoomData>，返回None

        end_index = text.find(send, start_index)
        if end_index == -1:
            return None  # 如果没有找到</RoomData>，返回None

        return text[start_index:end_index + len(send)]

    # 选择一个聊天，查看聊天记录
    def on_contact_select(self, event):
        global rdb
        global wpath
        selected_contact = self.contact_list.GetStringSelection()
        # 这里可以添加代码，以根据选中的联系人读取聊天记录并显示

        # 以双引号字符进行分割
        parts = selected_contact.split('###')
        wechat_id = parts[0]
        wechat_name = parts[1]
        #print(wechat_id)
        wechat_md5 = hashlib.md5(wechat_id.encode('utf-8')).hexdigest()  # 计算用户名的 MD5 哈希值
        # 生成聊天记录表
        chat_table_name = 'Chat_' + wechat_md5
        print(chat_table_name)
        # 如果是群聊，@chatroom，显示群成员
        if "@chatroom" in selected_contact :
            self.chatroom_contact(wechat_id)

        #print(chat_table_name)
        # hu获取数据库多个文件
        mfiles_lists = self.find_message_files(rdb)
        #print(mfiles_lists)
        db_if = 0
        db_file = ""
        for filename in mfiles_lists:
            db_path = rdb + filename  #'MM.sqlite'
            # 连接数据库并读取联系人列表
            connection = sqlite3.connect(db_path)
            cursor = connection.cursor()
            # 查询 sqlite_master 表以检查表是否存在
            cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?;", (chat_table_name,))
            result = cursor.fetchone()
            if result is not None:
                #print(f"表 '{table_name}' 存在。")
                db_if = 1 # 普通信息表
                db_file = db_path
                connection.close()
                break
            else:
                connection.close()
                #print(f"表 '{table_name}' 不存在。")

            #query= 'SELECT count() as cun FROM sqlite_sequence  where name = "' + chat_table_name + '"'
            #cursor.execute(query)  # 假设联系人表为Contacts、昵称字段为NickName
            #rows = cursor.fetchall()

            #self.contact_list.Clear()
            #for row in rows:
            #    cun = row[0]  # 获取该聊天表的计数 = 0 不存在
            #    if cun ==0 :
            #        db_if = 0
            #    else:
            #        db_if = 1
            #        db_file = db_path
            #        break  # 找到后立即退出循环
            #if db_if == 1 :
            #    break

        if db_if == 0 :
            # 检查在公众号数据库中是否存在
            ghdb_path = wpath + "Brand/BrandMsg.db"
            # 连接数据库并读取联系人列表
            connection = sqlite3.connect(ghdb_path)
            cursor = connection.cursor()
            # 查询 sqlite_master 表以检查表是否存在
            cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?;", (chat_table_name,))
            result = cursor.fetchone()
            if result is not None:
                # print(f"表 '{table_name}' 存在。")
                db_if = 2 # 存在公众号表
                db_file = ghdb_path
                connection.close()
                #print(ghdb_path)
            else:
                connection.close()
                print(wechat_id, wechat_name, "没有聊天记录！")
                self.chat_display.Clear()
                # self.chat_display.SetLabel("")
                return

        print(db_file)
        print(wechat_id, wechat_name, "查看聊天记录！")
        try:
            #读取聊天记录，显示
            # 执行查询操作
            connection = sqlite3.connect(db_file)
            # 设置游标的行工厂为sqlite3.Row，以便使用字段名访问
            # connection.row_factory = sqlite3.Row
            cursor = connection.cursor()
            query = f"SELECT CreateTime, MesLocalID, Type, Message, Status,MesSvrID FROM {chat_table_name} ORDER BY CreateTime ASC"
            cursor.execute(query)
            chat_records = cursor.fetchall()
            #conn.close()  # 关闭数据库连接

            # 清空聊天记录显示框
            self.chat_display.Clear()
            #self.chat_display.SetLabel("")

            for index, record in enumerate(chat_records):
                print("#", end='')  # 以不换行的方式打印“#”，打印进度条
                if index % 150 == 0 and index > 0:
                    print(index)
                self.display_message(wechat_id,wechat_name,wechat_md5,record,db_if)
            print()
        except sqlite3.Error as e:
            wx.MessageBox(f'数据库读取失败: {e}', '错误', wx.OK | wx.ICON_ERROR)
        finally:
            # 确保数据库连接在结束时关闭
            if connection:
                connection.close()

if __name__ == '__main__':
    #app = wx.App(False)
    #viewer = WeChatContactViewer()
    #filelst_path = '/Volumes/ssknvme512/weixin_Backup/Documents/LocalInfo.lst'
    #viewer.read_config(filelst_path)  # 传入配置文件路径
    #viewer.Show()
    #app.MainLoop()
    # print(sys.path)
    app = wx.App(False)
    viewer = WeChatContactViewer()
    viewer.Show()
    app.MainLoop()